package com.swz.gen;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharSequenceUtil;
import com.swz.gen.domain.ColumnInfo;
import com.swz.gen.domain.TableConfigInfo;
import com.swz.gen.enums.DataModelMetaConstants;
import com.swz.gen.enums.GenFileTypeEnums;
import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.File;
import java.io.FileWriter;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 1.重新生成时需要手动将原来生成的包文件删除掉 !!!!
 * 2.配置信息看着下面的注释去改
 */
public class GeneratorMain {

    // TODO: 1.这里改成模版文件的绝对路径地址, 注意不要加上 \\
    private static final String TEMPLATE_PATH = "D:\\Project\\xml-code-generation\\src\\main\\java\\com\\swz\\gen\\templates";
    // TODO: 2.这里改成文件生成的本地绝对路径地址 , 注意不要加上 \\
    private static final String GEN_CODE_PLACE = "D:\\Project\\xml-code-generation\\src\\main\\java\\com\\swz\\gen\\templates\\gencodeplace";

    // 存放相关的配置信息
    private static List<TableConfigInfo> tableConfigInfos = new ArrayList<>();

    // TODO: 3.在这里 构建你的相关配置信息
    static{

        //2.1第一张表配置
        TableConfigInfo tableConfigInfo1 = new TableConfigInfo(
                "jdbc:mysql://22.50.6.9:3306/syt?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true",
                "syt", //数据库用户名
                "syt_test", //数据库密码
                null, //mysql 的可以不用给,ORACLE的根据情况，看是在哪个表空间中
                "t_bank_activation_info", //表名称
                "com.suixingpay.xxm.core", //生成代码内的包的名称(注！！！！：随项目名称及位置进行修改 --->>例如:com.suixingpay.cps.user.service.user )
                "BankActivationInfo" //ORM映射POJO实体类的名称
        );

        ////2.1第一张表配置
        //TableConfigInfo tableConfigInfo2 = new TableConfigInfo(
        //        "jdbc:mysql://22.50.6.9:3306/syt?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true",
        //        "syt", //数据库用户名
        //        "syt_test", //数据库密码
        //        null, //mysql 的可以不用给,ORACLE的根据情况，看是在哪个表空间中
        //        "t_bank_payouts_flow", //表名称
        //        "com.suixingpay.xxm.core", //生成代码内的包的名称(注！！！！：随项目名称及位置进行修改 --->>例如:com.suixingpay.cps.user.service.user )
        //        "BankPayoutsFlow" //ORM映射POJO实体类的名称
        //);

        ////2.2第二张表配置
        //TableConfigInfo tableConfigInfo2 = new TableConfigInfo(
        //        "jdbc:oracle:thin:@22.50.100.108:1521/ntestdb",
        //        "cps_user",//数据库用户名
        //        "cps_user_test",  //数据库密码
        //        "CPS_USER", //mysql 的可以不用给,ORACLE的根据情况，看是在哪个表空间中
        //        "TEST_1",  //表名称
        //        "com.suixingpay.xxm.core", //生成代码内的包的名称(注！！！！：随项目名称及位置进行修改 --->>例如:com.suixingpay.cps.user.service.user )
        //        "Test1"  //ORM映射POJO实体类的名称
        //);

        // TODO: 4.注意这里要加上配置
        tableConfigInfos.add(tableConfigInfo1);
        //tableConfigInfos.add(tableConfigInfo2);
    }


    public static void main (String[] args) throws Exception{
        //校验用户的配置信息
        validCustomerConfigArgs();

        for (TableConfigInfo tableConfigInfo : tableConfigInfos) {
            Configuration cfg = createTemplateConfiguration();
            Map<String, Object> dataModel = initAllTemplateMetaArgs(cfg, tableConfigInfo);
            System.out.println("生成表:>>>>>>>>" + tableConfigInfo.getTableName());
            //生成Mapper.java
            createFromTemplate("MyBatisMapperTemplate.ftl", "Mapper.java", cfg, dataModel, tableConfigInfo, GenFileTypeEnums.MYBATIS_INTERFACE);
            // 生成Service.java
            createFromTemplate("ServicecTemplate.ftl", "Service.java", cfg, dataModel, tableConfigInfo, GenFileTypeEnums.SERVICE);
            // 生成Mapper.xml
            createFromTemplate("MyBatisMapperXmlTemplate.ftl", "Mapper.xml", cfg, dataModel, tableConfigInfo, GenFileTypeEnums.MYBATIS_XML);
            // 生成Domain.java
            createFromTemplate("DomainTemplate.ftl", ".java", cfg, dataModel, tableConfigInfo, GenFileTypeEnums.DOMAIN);
        }
    }

    private static void validCustomerConfigArgs (){
        if (TEMPLATE_PATH.endsWith("\\") || GEN_CODE_PLACE.endsWith("\\")) {
            System.err.println("TEMPLATE_PATH,GEN_CODE_PLACE位置变量不要以 \\\\结尾！！！");
            throw new IllegalArgumentException();
        }

        tableConfigInfos.forEach(tableConfigInfo -> {
            String dbSchema = tableConfigInfo.getDbSchema();
            String jdbcUrl = tableConfigInfo.getJdbcUrl();
            String jdbcUser = tableConfigInfo.getJdbcUser();
            String jdbcPassword = tableConfigInfo.getJdbcPassword();
            String tableName = tableConfigInfo.getTableName();
            String packageName = tableConfigInfo.getPackageName();
            String className = tableConfigInfo.getClassName();

            Assert.notBlank(jdbcUrl);
            Assert.notBlank(jdbcUser);
            Assert.notBlank(jdbcPassword);
            Assert.notBlank(tableName);
            Assert.notBlank(packageName);
            Assert.notBlank(className);
            String dbType = getDbType(tableConfigInfo);
            if (dbType.equals("oracle")) {
                Assert.notBlank(dbSchema);
            }
        });
    }


    private static Configuration createTemplateConfiguration () throws Exception{
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
        cfg.setDirectoryForTemplateLoading(new File(TEMPLATE_PATH));  // 模板路径
        cfg.setDefaultEncoding("UTF-8");
        return cfg;
    }

    private static Map<String, Object> initAllTemplateMetaArgs (Configuration cfg, TableConfigInfo tableConfigInfo) throws Exception{

        List<ColumnInfo> columns = getTableColumns(tableConfigInfo);
        cfg.setDirectoryForTemplateLoading(new File(TEMPLATE_PATH));  // 模板路径
        cfg.setDefaultEncoding("UTF-8");
        Map<String, Object> dataModel = new HashMap<>();
        dataModel.put(DataModelMetaConstants.JDBC_URL, tableConfigInfo.getJdbcUrl());
        dataModel.put(DataModelMetaConstants.JDBC_USER, tableConfigInfo.getJdbcUser());
        dataModel.put(DataModelMetaConstants.JDBC_PASSWORD, tableConfigInfo.getJdbcPassword());
        dataModel.put(DataModelMetaConstants.DB_SCHEMA, tableConfigInfo.getDbSchema());
        dataModel.put(DataModelMetaConstants.TABLE_NAME, tableConfigInfo.getTableName());
        dataModel.put(DataModelMetaConstants.CLASS_NAME, tableConfigInfo.getClassName());
        dataModel.put(DataModelMetaConstants.PACKAGE_NAME, tableConfigInfo.getPackageName());
        dataModel.put(DataModelMetaConstants.COLUMNS, columns);

        // 校验主键约束,目前只支持一个主键
        List<ColumnInfo> collect = columns.stream().filter(ColumnInfo::getIsPrimaryKey).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(collect) || collect.size() != 1) {
            System.err.println("目前只支持一个主键的表!");
            throw new IllegalArgumentException("没有主键，或主键数量>1,不支持生成XML");
        }
        dataModel.put(DataModelMetaConstants.ID_COLUM, collect.get(0).getName());
        dataModel.put(DataModelMetaConstants.ID_PROPERTY, collect.get(0).getProperty());

        // 获取字符串的第一个字符
        char firstChar = tableConfigInfo.getClassName().charAt(0);
        String classNameLower = "";
        if (Character.isUpperCase(firstChar)) {
            // 将第一个字符转换为小写
            firstChar = Character.toLowerCase(firstChar);
            // 使用 StringBuilder 构建新的字符串
            StringBuilder sb = new StringBuilder(tableConfigInfo.getClassName());
            sb.setCharAt(0, firstChar);
            classNameLower = sb.toString();
        }

        dataModel.put(DataModelMetaConstants.CLASS_NAME_LOWER, classNameLower);
        dataModel.put(DataModelMetaConstants.DB_TYPE, getDbType(tableConfigInfo));
        return dataModel;
    }

    //获取数据源类型
    private static String getDbType (TableConfigInfo tableConfigInfo){
        String jdbcUrl = tableConfigInfo.getJdbcUrl();
        if (jdbcUrl.contains("mysql")) {
            return "mysql";
        } else if (jdbcUrl.contains("oracle")) {
            return "oracle";
        } else {
            System.err.println("根据配置,获取数据源类型错误!!!");
            throw new IllegalArgumentException();
        }
    }

    private static void createFromTemplate (String templateName,
                                            String fileNameSufix,
                                            Configuration cfg,
                                            Map<String, Object> dataModel,
                                            TableConfigInfo configInf,
                                            GenFileTypeEnums genFileTypeEnums) throws Exception{
        Template template = cfg.getTemplate(templateName);

        String place = GEN_CODE_PLACE;
        if (!place.endsWith("\\")) {
            place = place + "\\";
        }

        // 不存在就创建
        String genPlace = place + genFileTypeEnums.getPackageName() + "\\";
        File directory = new File(genPlace);
        if (!FileUtil.exist(directory)) {
            FileUtil.mkdir(directory);
        }

        try (FileWriter writer = new FileWriter(genPlace + configInf.getClassName() + fileNameSufix)) {
            template.process(dataModel, writer);
            System.out.println("文件 生成成功！文件名：" + configInf.getClassName() + fileNameSufix);
        }
    }


    private static List<ColumnInfo> getTableColumns (TableConfigInfo tableConfigInfo) throws Exception{
        List<ColumnInfo> columns = new ArrayList<>();
        try (Connection connection = DriverManager.getConnection(tableConfigInfo.getJdbcUrl(), tableConfigInfo.getJdbcUser(), tableConfigInfo.getJdbcPassword())) {
            DatabaseMetaData metaData = connection.getMetaData();

            ResultSet rs = metaData.getColumns(null, null, tableConfigInfo.getTableName(), null);
            while (rs.next()) {
                String columnName = rs.getString("COLUMN_NAME");
                String dataType = rs.getString("TYPE_NAME");
                String remarks = rs.getString("REMARKS") == null ? "" : rs.getString("REMARKS"); // 这里要判空，否则Freemarker为null会填充错误
                columns.add(new ColumnInfo(
                        isPrimaryKey(metaData, tableConfigInfo.getTableName(), columnName),
                        columnName,
                        toCamelCase(columnName),
                        getJdbcType(dataType),
                        getJavaType(dataType),
                        remarks
                ));
            }
        }
        return columns;
    }


    private static boolean isPrimaryKey (DatabaseMetaData metaData, String tableName, String columnName) throws SQLException{
        // 获取主键列名
        List<String> primaryKeyColumns = new ArrayList<>();
        ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, tableName);
        while (primaryKeys.next()) {
            primaryKeyColumns.add(primaryKeys.getString("COLUMN_NAME"));
        }
        primaryKeys.close();
        // 判断列是否为主键
        return primaryKeyColumns.contains(columnName);
    }

    // 转驼峰
    private static String toCamelCase (String columnName){
        String camelCase = CharSequenceUtil.toCamelCase(columnName);
        //如果第一位是大写， 这里全变成小写
        char firstChar = camelCase.charAt(0);
        if (Character.isUpperCase(firstChar)) {
            camelCase = camelCase.toLowerCase();
        }
        return camelCase;
    }

    // 不知道枚举完了没，有问题再加
    private static String getJdbcType (String sqlType){
        sqlType = changeSqlType(sqlType);

        switch (sqlType.toUpperCase()) {
            case "VARCHAR":
                return "VARCHAR";
            case "CHAR":
                return "CHAR";
            case "DECIMAL":
                return "DECIMAL";
            case "DATETIME":
                return "TIMESTAMP";
            case "TIMESTAMP":
                return "TIMESTAMP";
            case "DATE":
                return "DATE";
            case "INTEGER":
                return "INTEGER";
            case "INT":
                return "INT";
            case "NUMBER":
                return "NUMBER";
            case "BIGINT":
                return "BIGINT";
            case "TINYINT":
                return "INTEGER";
            default:
                System.err.println("getJdbcType >>========= " + sqlType + " 没有映射上，默认给值VARCHAR");
                return "VARCHAR";
        }
    }

    // 还没有枚举完，有问题再加
    private static String getJavaType (String sqlType){
        sqlType = changeSqlType(sqlType);
        switch (sqlType.toUpperCase()) {
            case "VARCHAR":
                return "String";
            case "CHAR":
                return "String";
            case "DECIMAL":
                return "BigDecimal";
            case "DATETIME":
                return "Date";
            case "TIMESTAMP":
                return "Date";
            case "DATE":
                return "Date";
            case "INTEGER":
                return "Integer";
            case "BIGINT":
                return "Long";
            case "INT":
                return "Integer";
            case "NUMBER":
                return "Long";
            case "TINYINT":
                return "Integer";
            default:
                System.err.println("getJavaType >>========= " + sqlType + " 没有映射上，默认给值String");
                return "String";
        }
    }

    private static String changeSqlType (String sqlType){
        //ORACLE 存在 VARCHAR2 TIMESTAMP(6)等搞法，这里全给转了
        sqlType = sqlType.startsWith("VARCHAR") ? "VARCHAR" : sqlType;
        sqlType = sqlType.startsWith("TIMESTAMP") ? "TIMESTAMP" : sqlType;
        return sqlType;
    }


}
